package ElectricCraft;

import ElectricCraft.frames.*;
import ElectricCraft.model.ChipModel;
import ElectricCraft.model.ModelPackage;
import ElectricCraft.model.ModelStateBuilder;
import ElectricCraft.viewers.NotificationListener;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.util.*;
import java.io.*;
import java.awt.event.*;
import java.util.List;

/**
 * ElectricCraft main window class.
 */
public class MainWindowFrame extends JFrame implements MainWindow {
    private final ModelPackage modelPackage;
    private final String packagePath;

    private final EditorsTabPanel tabPanel;

    private static final String MAIN_HEADER = "ElectricCraft 0.1";

    private final DebugPane debugPanel;

	public MainWindowFrame(Properties properties) {
		super(MAIN_HEADER);

		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

		createMenu();

        packagePath = properties.getProperty("package.path");
        modelPackage = loadPackage();

        setLayout(new BorderLayout());

        // split pane
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);

        // package tree to the left
        PackageViewTree packageTree = new PackageViewTree(this, true);
        splitPane.setLeftComponent(packageTree);

        splitPane.setDividerLocation(220);
        splitPane.setDividerSize(2);

        // tab panel to the right
        JPanel rightPanel = new JPanel(new BorderLayout());

        debugPanel = new DebugPane(this);
        rightPanel.add(debugPanel, BorderLayout.SOUTH);

        tabPanel = new EditorsTabPanel(this);
        rightPanel.add(tabPanel, BorderLayout.CENTER);

        tabPanel.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                debugPanel.processCurrentEditorChanged();
            }
        });

        splitPane.setRightComponent(rightPanel);
        add(splitPane, BorderLayout.CENTER);

        // TODO: store all sizes and dividers positions

        // TODO: save should save only current diagram
        // TODO: add "save all" menu
        // TODO: add "save as..." menu to allow to save inner models same as outer

		setSize(800, 600);
		setVisible(true);
	}

    public ModelPackage getPackage() {
        return modelPackage;
    }

    public JFrame getFrame() {
        return this;
    }

    public ChipEditorPane getCurrentEditor() {
        return tabPanel.getCurrentEditor();
    }

    public void setCurrentEditor(ChipEditorPane editor) {
        int index = tabPanel.indexOfComponent(editor);
        if (index == -1) {
            return ;
        }

        tabPanel.setSelectedIndex(index);
    }

    public ChipEditorPane getEditorByModel(ChipModel model) {
        for (int k = 0; k < tabPanel.getTabCount(); ++k) {
            ChipEditorPane editor = (ChipEditorPane)tabPanel.getComponent(k);
            if (editor.getOriginalModel() == model) {
                return editor;
            }
        }

        return null;
    }

    public void closeEditor(ChipEditorPane editor) {
        int index = tabPanel.indexOfComponent(editor);
        if (index == -1) {
            return ;
        }

        tabPanel.removeTabAt(index);
        openedEditors.remove(editor);

        debugPanel.processCurrentEditorChanged();
    }

    public void updateModelNames() {
        for (ChipEditorPane editor : openedEditors) {
            int index = tabPanel.indexOfComponent(editor);

            editor.updateModelName();
            tabPanel.setTitleAt(index, getTabName(editor));
        }
    }

    private ModelPackage loadPackage() {
        ModelPackage pack = new ModelPackage();
        File packageFile = new File(packagePath);
        try {
            if (packageFile.exists()) {
                pack.load(packageFile.getPath());
            }
        }
        catch (Exception e) {
            JOptionPane.showMessageDialog(
                this,
                String.format("Failed to load package: %s\n\n%s", packagePath, e.getMessage()),
                "Fail", JOptionPane.ERROR_MESSAGE);

            e.printStackTrace();

            System.exit(-1);
        }

        return pack;
    }

	private void createMenu() {
        JMenuBar menuBar = new JMenuBar();

        //////////////////////////
        // File menu
		JMenu file = new JMenu("File");

        JMenuItem saveItem = new JMenuItem("Save");
        file.add(saveItem);

		file.addSeparator();

		JMenuItem exitItem = new JMenuItem("Exit");
		file.add(exitItem);

        menuBar.add(file);

        ////////////////////////////
        // Debug menu
        JMenu debugMenu = new JMenu("Debug");

        JMenuItem startDebugItem = new JMenuItem("Start Debugging");
        debugMenu.add(startDebugItem);

        menuBar.add(debugMenu);

        ////////////////////////////
        // Help menu
        JMenu helpMenu = new JMenu("Help");
        JMenuItem aboutItem = new JMenuItem("About");
        helpMenu.add(aboutItem);
        menuBar.add(helpMenu);

        setJMenuBar(menuBar);

        ////////////////////////////
		// Actions
        saveItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                savePackage(true);
            }
        });

        exitItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                processClosing();
            }
        });

        startDebugItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                startDebugging();
            }
        });

        aboutItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(getFrame(),
                        String.format("%s\n\nCopyright \u00A9 Evgeniy A. Cymbalyuk.", MAIN_HEADER),
                        "About", JOptionPane.INFORMATION_MESSAGE);
            }
        });

        addWindowListener(
                new WindowAdapter() {
                    @Override
                    public void windowClosing(WindowEvent e) {
                        processClosing();
                    }
                });
	}

    private void startDebugging() {
        ChipEditorPane editor = getCurrentEditor();
        if (editor == null) {
            return ;
        }

        ModelStateBuilder.build(editor.getModel());
    }

    public boolean savePackage(boolean saveAll) {
        if (saveAll) {
            for (ChipEditorPane editor : openedEditors) {
                editor.save();
            }
        }

        try {
            modelPackage.save(packagePath);
            return true;
        }
        catch (IOException ex) {
            JOptionPane.showMessageDialog(this, String.format("Failed to save package\n\n%s", ex.getMessage()));
            return false;
        }
    }

    private final List<ChipEditorPane> openedEditors = new LinkedList<ChipEditorPane>();
    public List<ChipEditorPane> getOpenedEditors() {
        return openedEditors;
    }

    public void openModel(final ChipModel model) {
        for (ChipEditorPane pane : openedEditors) {
            if (pane.getOriginalModel() == model) {
                tabPanel.setSelectedComponent(pane);

                return ;
            }
        }

        final ChipEditorPane editor = new ChipEditorPane(this, model);

        tabPanel.addTab(model.name, editor);

        openedEditors.add(editor);

        editor.addChangedStatusListener(
                new NotificationListener() {
                    public void notified() {
                        // update tab name
                        tabPanel.setTitleAt(
                                tabPanel.indexOfComponent(editor), getTabName(editor));
                    }
                });
    }

    private static String getTabName(ChipEditorPane editor) {
        String prefix = editor.isChanged() ? "* " : "";

        ChipViewerPane.ModelPath path = editor.getModelPath();
        if (path.size() == 1) {
            return prefix + path.getLast().name;
        }

        String rootName = path.getRoot().name;
        String lastName = path.getLast().name;

        return String.format("%s%s: %s", prefix, rootName, lastName);
    }

    private void processClosing() {
        for (ChipEditorPane editor : openedEditors) {
            if (!editor.processClose()) {
                return ;
            }
        }

        if (!savePackage(false)) {
            return ;
        }

        System.exit(0);
    }

    private static Properties defaultProps;
    private static Properties getDefaults() {
        if (defaultProps != null) {
            return defaultProps;
        }

        Properties props = defaultProps = new Properties();
        props.setProperty("package.path", "chips.package");

        return props;
    }

    private static final String SETTINGS_PATH = "ElectricCraft.properties";
    public static void main(String [] args) {
        Properties properties = new Properties(getDefaults());

        // TODO: don't start if another instance is already running

        try {
            File file = new File(SETTINGS_PATH);
            if (file.exists()) {
                properties.load(new FileReader(file));
            } else {
                getDefaults().store(new FileWriter(file), "ElectricCraft default properties");
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        new MainWindowFrame(properties);
    }
}
